﻿/**********************************************************************
 * Copyright (c) 2008, j. montgomery                                  *
 * All rights reserved.                                               *
 *                                                                    *
 * Redistribution and use in source and binary forms, with or without *
 * modification, are permitted provided that the following conditions *
 * are met:                                                           *
 *                                                                    *
 * + Redistributions of source code must retain the above copyright   *
 *   notice, this list of conditions and the following disclaimer.    *
 *                                                                    *
 * + Redistributions in binary form must reproduce the above copyright*
 *   notice, this list of conditions and the following disclaimer     *
 *   in the documentation and/or other materials provided with the    *
 *   distribution.                                                    *
 *                                                                    *
 * + Neither the name of j. montgomery's employer nor the names of    *
 *   its contributors may be used to endorse or promote products      *
 *   derived from this software without specific prior written        *
 *   permission.                                                      *
 *                                                                    *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS*
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  *
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS  *
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE     *
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,*
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES           *
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR *
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) *
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,*
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)      *
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED*
 * OF THE POSSIBILITY OF SUCH DAMAGE.                                 *
 **********************************************************************/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

using System.Net;
using System.IO;
using System.Web.Security;

using UpdateDnsOMatic.Enums;
using UpdateDnsOMatic.Configuration;

namespace UpdateDnsOMatic.Service
{
    /// <summary>
    /// API Documentation for Dns-O-Matic: https://www.dnsomatic.com/wiki/api
    /// </summary>
    public class DnsOMaticClient
    {
        private Dictionary<string, ReturnCode> _returnCodes = new Dictionary<string, ReturnCode>();

        public DnsOMaticClient()
        {
            _returnCodes.Add("badauth", ReturnCode.badauth);
            _returnCodes.Add("notfqdn", ReturnCode.notfqdn);
            _returnCodes.Add("nochg", ReturnCode.nochg);
            _returnCodes.Add("nohost", ReturnCode.nohost);
            _returnCodes.Add("numhost", ReturnCode.numhost);
            _returnCodes.Add("abuse", ReturnCode.abuse);
            _returnCodes.Add("badagent", ReturnCode.badagent);
            _returnCodes.Add("good", ReturnCode.good);
            _returnCodes.Add("dnserr", ReturnCode.dnserr);
            _returnCodes.Add("911", ReturnCode._911);
        }

        public bool UpdateDnsOMatic(out string message)
        {
            // Only update if it's changed
            bool doUpdate = false;
            string ipaddress = string.Empty;
            
            try
            {
                // Detect Public IP Address
                ipaddress = this.RetrievePublicIP();
                Trace.WriteLine("Detected current public IP address as " + ipaddress);
            }
            catch (Exception e)
            {
                message = "Error detecting current public IP Address: " + e.Message;
                return false;
            }

            doUpdate = !(ipaddress == DnsOMaticConfiguration.Instance.LastKnownIP);
            
            if (doUpdate)
            {

                string username = DnsOMaticConfiguration.Instance.UserName;
                string password = DnsOMaticConfiguration.Instance.Password;
                string dnsOMaticUpdateServer = DnsOMaticConfiguration.Instance.ServerAddress;
                string yourhostname = DnsOMaticConfiguration.Instance.HostNameToUpdate;

                // save last seen IP
                DnsOMaticConfiguration.Instance.LastKnownIP = ipaddress;
                DnsOMaticConfiguration.Instance.Save();

                string wildcard = DnsOMaticConfiguration.Instance.Wildcard;
                string mx = DnsOMaticConfiguration.Instance.MX;
                string backmx = DnsOMaticConfiguration.Instance.BackupMX;

                string uri = string.Format("https://{0}/nic/update?hostname={1}&myip={2}&wildcard={3}&mx={4}&backmx={5}", dnsOMaticUpdateServer, yourhostname, ipaddress, wildcard, mx, backmx);
                string responseString = string.Empty;

                try
                {
                    WebRequest request = WebRequest.Create(uri);
                    request.Proxy = Configuration.ProxySettings.GetProxy(true);

                    request.Credentials = new NetworkCredential(username, password);
                    ((HttpWebRequest)request).UserAgent = "IASON - DnsOMatic Windows Service - 0.8";

                    // Send request, get response, and parse out the IP address on the page. 
                    using (WebResponse res = request.GetResponse())
                    {
                        using (Stream s = res.GetResponseStream())
                        using (StreamReader sr = new StreamReader(s, Encoding.UTF8))
                        {
                            responseString = sr.ReadToEnd();
                        }
                    }
                    Trace.WriteLine("Response: " + responseString);
                    // SUCCESS 
                    // "good <ip> <hostname> <service>"
                    // ERROR 
                    // "‹code› <ip> <hostname> [<service>]"
                    string[] returnValues = responseString.Split(new char[] { ' ' });
                    if (returnValues.Length >= 1)
                    {
                        string code = returnValues[0];

                        string ip = string.Empty;
                        if (returnValues.Length > 1)
                            ip = returnValues[1];

                        string hostname = string.Empty;
                        if (returnValues.Length > 2)
                            hostname = returnValues[2];

                        string service = string.Empty;
                        if (returnValues.Length == 3)
                            service = returnValues[3];

                        switch (_returnCodes[code])
                        {
                            case ReturnCode._911:
                                message = "There is a problem or scheduled maintenance on DNS-O-Matic. Stop updating for 30 minutes, and optionally ask the user to contact DNS-O-Matic.";
                                return false;
                            case ReturnCode.abuse:
                                message = "The hostname is blocked for update abuse.";
                                return false;
                            case ReturnCode.badagent:
                                message = "The user-agent is blocked.";
                                return false;
                            case ReturnCode.badauth:
                                message = "The DNS-O-Matic username or password specified are incorrect. No updates will be distributed to services until this is resolved.";
                                return false;
                            case ReturnCode.dnserr:
                                message = "DNS error encountered. Stop updating for 30 minutes, and optionally ask the user to contact DNS-O-Matic.";
                                return false;
                            case ReturnCode.nohost:
                                message = "The hostname passed could not be matched to any services configured. The service field will be blank in the return code.";
                                return false;
                            case ReturnCode.notfqdn:
                                message = "The hostname specified is not a fully-qualified domain name. If no hostnames included, notfqdn will be returned once.";
                                return false;
                            case ReturnCode.numhost:
                                message = "You may update up to 20 hosts. numhost is returned if you try to update more than 20 or update a round-robin.";
                                return false;
                            case ReturnCode.good:
                                {
                                    if (ip.Equals(ipaddress))
                                    {
                                        message = "Update successful.";
                                        // SUCCESS
                                        return true;
                                    }
                                    else
                                    {
                                        message = "WARNING: request was ignored because of agent that does not follow specifications. Sent IP " + ipaddress + " service reported  " + ip;
                                        return false;
                                    }
                                }
                            case ReturnCode.nochg:
                                message = "Update successful.";
                                return true;
                            default:
                                {
                                    message = "Unknown response: " + responseString;
                                    return false;
                                }
                        }
                    }
                    else
                    {
                        message = "Unexpected response from server: " + responseString;
                        return false;
                    }

                }
                catch (Exception ex)
                {
                    message = "An error occured trying to update: " + ex.ToString();
                    return false;
                }
            }
            else
            {
                message = "IP hasn't changed - update not required.";
                return false;
            }
        }

        private string RetrievePublicIP()
        {
            string uri = String.Format("http://{0}/", DnsOMaticConfiguration.Instance.PublicIpDiscoverServerAddress);
            WebRequest getPublicIpRequest = WebRequest.Create(uri);
            getPublicIpRequest.Proxy = Configuration.ProxySettings.GetProxy(true); 
            // Send request, get response, and parse out the IP address on the page. 
            using (WebResponse res = getPublicIpRequest.GetResponse())
            {
                using (Stream s = res.GetResponseStream())
                using (StreamReader sr = new StreamReader(s, Encoding.UTF8))
                {
                    return sr.ReadToEnd();
                }
            }
        }
    }
}
